package jdec.jdt.ls.decompiler.common;

import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.IBuffer;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.compiler.env.IBinaryType;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.core.BufferManager;
import org.eclipse.jdt.internal.core.ClassFile;
import org.eclipse.jdt.internal.core.PackageFragmentRoot;
import org.eclipse.jdt.internal.core.SourceMapper;
import org.eclipse.jdt.ls.core.internal.JDTUtils;
import org.eclipse.jdt.ls.core.internal.preferences.Preferences;
import jdec.jdt.ls.decompiler.common.util.DecompilerOutputUtil;
import jdec.jdt.ls.decompiler.common.util.ReflectionUtils;
import jdec.jdt.ls.decompiler.common.util.SourceMapperUtil;


public abstract class BaseDecompilerSourceMapper extends DecompilerSourceMapper {
    protected CachingDecompiler origionalDecompiler;
    private static Map<String, String> compilerOptions = new HashMap<>();

    @Override
    public void setPreferences(Preferences preferences) {
        //
    }

    static {
        CompilerOptions option = new CompilerOptions();
        compilerOptions = option.getMap();
        compilerOptions.put(CompilerOptions.OPTION_Compliance, DecompilerOutputUtil.getMaxDecompileLevel()); // $NON-NLS-1$
        compilerOptions.put(CompilerOptions.OPTION_Source, DecompilerOutputUtil.getMaxDecompileLevel()); // $NON-NLS-1$
    }

    public BaseDecompilerSourceMapper(IPath sourcePath, String rootPath) {
        this(sourcePath, rootPath, compilerOptions);
    }

    public BaseDecompilerSourceMapper(IPath sourcePath, String rootPath, Map<String, String> options) {
        super(sourcePath, rootPath, options);
    }

    /** 可能有jdt.ls或者classFile调用 */
    @Override
    public char[] findSource(IType type, IBinaryType info) {
        // 每次解析一次缓存判断，因为如果在classFile内部调用不走 this.setPreferences(Preferences preferences)
        Preferences preferences = JavaDecompilerPlugin.getDefault().getPreferences();
        boolean enableCache = JavaDecompilerPlugin.getDefault().isEnableCache();

        IPackageFragment pkgFrag = type.getPackageFragment();
        IPackageFragmentRoot root = (IPackageFragmentRoot) pkgFrag.getParent();

        char[] attachedSource = null;

        // 从缓存获取
        if (enableCache && originalSourceMapper.containsKey(root)) {
            attachedSource = originalSourceMapper.get(root).findSource(type, info);

            if (attachedSource != null) {
                isAttachedSource = true;
                SourceMapperUtil.updateSourceRangesFromParentClassFile(type, attachedSource);
                SourceMapperUtil.mapSourceMapper(this, type, attachedSource, info);
                SourceMapperUtil.mapSourceMapper(((PackageFragmentRoot) root).getSourceMapper(), type, attachedSource, info);
                return attachedSource;
            }
        }


        try {
            PackageFragmentRoot pfr = (PackageFragmentRoot) root;
            SourceMapper sourceMapper = pfr.getSourceMapper();

            if (!enableCache && sourceMapper == this) {
                // 等待切换反编译器
                return null;
            }

            if (enableCache && !originalSourceMapper.containsKey(root)) {
                ReflectionUtils.setFieldValue(this, "options", //$NON-NLS-1$
                        ReflectionUtils.getFieldValue(sourceMapper, "options"));
                originalSourceMapper.put(root, sourceMapper);
            }

            // 使用原始源码映射
            if (sourceMapper != null && !(sourceMapper instanceof DecompilerSourceMapper)) {
                attachedSource = sourceMapper.findSource(type, info);

                if (attachedSource != null) {
                    isAttachedSource = true;
                    if (enableCache) {
                        SourceMapperUtil.updateSourceRangesFromParentClassFile(type, attachedSource);
                        SourceMapperUtil.mapSourceMapper(this, type, attachedSource, info);
                        SourceMapperUtil.mapSourceMapper(((PackageFragmentRoot) root).getSourceMapper(), type, attachedSource, info);
                    }
                    return attachedSource;
                }
            }

            // 替换原始源码映射
            if (enableCache && sourceMapper != this) {
                pfr.setSourceMapper(this);
            }

        } catch (JavaModelException e) {
            JavaDecompilerPlugin.logError(e, "Could not set source mapper."); //$NON-NLS-1$
        }

        // 调用反编译
        String code = null;
        try {
            // 1. set settings
            origionalDecompiler.setDecompilePreferences(preferences);
            // 2. get source
            code = origionalDecompiler.getSource(type, new NullProgressMonitor());
        } catch (CoreException e) {
            JavaDecompilerPlugin.logError(e, "origionalDecompiler.getSource"); //$NON-NLS-1$
        }

        if (code == null) {
            return null;
        }

        DecompilerOutputUtil decompilerOutputUtil = new DecompilerOutputUtil(origionalDecompiler.getDecompilerType(), code);
        code = decompilerOutputUtil.realign();


        char[] source = code.toCharArray();

        if (enableCache && originalSourceMapper.containsKey(root)) {
            SourceMapper rootSourceMapper = originalSourceMapper.get(root);
            if (rootSourceMapper.findSource(type, info) == null) {
                SourceMapperUtil.mapSourceMapper(rootSourceMapper, type, source, info);
            }
        }

        if (enableCache) {
            SourceMapperUtil.updateSourceRangesFromParentClassFile(type, source);
        }

        return source;
    }


    /** 由jdt.ls调用 */
    @Override
    public String getSource(IClassFile classFile, IProgressMonitor monitor) throws CoreException {
        boolean enableCache = JavaDecompilerPlugin.getDefault().isEnableCache();

        // 同步源码关联
        JavaDecompilerPlugin.getDefault().probeAttachSource(classFile);

        if (enableCache) {
            String src = classFile.getSource();
            if (src != null) {
                return src;
            }
        }


        char[] findSrc = findSource(classFile.getType());
        if (findSrc == null) {
            return null;
        }

        // 添加缓存
        if (enableCache) {
            IBuffer classBuffer = BufferManager.createBuffer(classFile);
            classBuffer.setContents(findSrc);
            JavaDecompilerBufferManager javaDecompilerBufferManager = JavaDecompilerBufferManager.getBufferManager();
            javaDecompilerBufferManager.addBuffer(classBuffer);

            SourceMapperUtil.mapSourceMapper(this, classFile.getType(), findSrc, null);
            SourceMapperUtil.bufferManagerUpdateSource(javaDecompilerBufferManager, (ClassFile) classFile, findSrc);
        }

        return new String(findSrc);
    }

    @Override
    public String getContent(URI uri, IProgressMonitor monitor) throws CoreException {
        IClassFile classFile = JDTUtils.resolveClassFile(uri);
        return getSource(classFile, monitor);
    }

    /* 反编译实现结束 */
    protected String getArchivePath(IPackageFragmentRoot root) {
        try {
            return root.getUnderlyingResource().getLocationURI().getPath();
        } catch (JavaModelException e) {
            throw new RuntimeException("Unexpected Java model exception: " //$NON-NLS-1$
                    + e.toString());
        }
    }
}
